#include "md2.h"
#include <cstdio>

/*==============================================================================
 Vectorial tools
==============================================================================*/

/*------------------------------------------------------------------------------
  Calcula el vector Normal a un poligono
------------------------------------------------------------------------------*/
void CalcularVectorNormal(float fVert1[3], float fVert2[3],float fVert3[3],
					      float *fNormalX,float *fNormalY,float *fNormalZ)
{
	float Qx, Qy, Qz, Px, Py, Pz;
	Px = fVert2[0]-fVert1[0];
	Py = fVert2[1]-fVert1[1];
	Pz = fVert2[2]-fVert1[2];
 
	Qx = fVert3[0]-fVert1[0];
	Qy = fVert3[1]-fVert1[1];
 	Qz = fVert3[2]-fVert1[2];
 
 	*(fNormalX) = Py*Qz - Pz*Qy;
	*(fNormalY) = Pz*Qx - Px*Qz;
 	*(fNormalZ) = Px*Qy - Py*Qx;
}

/*------------------------------------------------------------------------------
 Calcula el Angulo entre 2 Vectores
------------------------------------------------------------------------------*/
float Angulo(float V1[3],float V2[3])
{	
	return (V1[0]*V2[0])+(V1[1]*V2[1])+(V1[2]*V2[2]);
}

/*------------------------------------------------------------------------------
 Calcula la longitud de un vector
------------------------------------------------------------------------------*/
float Magnitud(float V[3])
{
	return sqrtf (V[0] * V[0] + V[1] * V[1] + V[2] * V[2]);
}

/*------------------------------------------------------------------------------
 Crea un vector con una longitud de Uno
------------------------------------------------------------------------------*/
void Normalizar (float V[3])
{
	// Calcula longitud
	float M = Magnitud (V);

	// Nos aseguramos de no dividir por 0
	if (M != 0.0f)
	{
		// Normalizamos las 3 componentes
		V[0] /= M;
		V[1] /= M;
		V[2] /= M;
	}
}

/*------------------------------------------------------------------------------
 Rota un vector usando la matriz pasada como parámetro
------------------------------------------------------------------------------*/
void Rotar (float M[16], float V[3], float D[3])				
{
	D[0] = (M[0] * V[0]) + (M[4] * V[1]) + (M[8]  * V[2]);
	D[1] = (M[1] * V[0]) + (M[5] * V[1]) + (M[9]  * V[2]);
	D[2] = (M[2] * V[0]) + (M[6] * V[1]) + (M[10] * V[2]);
}
/*============================================================================*/


/*------------------------------------------------------------------------------
	Constructor
------------------------------------------------------------------------------*/
CMd2::CMd2()
{
	m_pFrames = 0;
	m_pTriangles = 0;
	m_pTexCoords = 0;
	m_pVerts = 0;
	
	m_fmax_X=0;
	m_fmax_Y=0;
	m_fmin_X=0;
	m_fmin_Y=0;
	
	m_fAnguloLuz[0]=-0.75f;
	m_fAnguloLuz[1]=0.50f;
	m_fAnguloLuz[2]=0.0f;
	Normalizar(m_fAnguloLuz);
	
	m_bDelineado=TRUE;
	m_AnchoLinea=4;
	m_ColorLinea[0]=0.0f;
	m_ColorLinea[1]=0.0f;
	m_ColorLinea[2]=0.0f;
}

/*------------------------------------------------------------------------------
	Constructor
------------------------------------------------------------------------------*/
CMd2::CMd2(const char * szFile)
{
	m_pFrames = 0;
	m_pTriangles = 0;
	m_pTexCoords = 0;
	m_pVerts = 0;
	Cargar(szFile);
}

/*------------------------------------------------------------------------------
	Destructor
------------------------------------------------------------------------------*/
CMd2::~CMd2()
{

}

/*------------------------------------------------------------------------------
	Loads MD2 model
------------------------------------------------------------------------------*/
bool CMd2::Cargar(const char * szFilename,const char szDirBase[255])
{
	unsigned char * ucpBuffer = 0;
	unsigned char * ucpPtr = 0;
	unsigned char * ucpTmpPtr = 0; 
	char path_md2[255];
	int iFileSize = 0;
	int i,j;
	FILE * f;
	
	strcpy(path_md2,"");
	strcpy(path_md2,szDirBase);
	strcat(path_md2,szFilename);
	
	//printf("PATH:%s\n",path_md2);
	
	if(!(f = fopen(path_md2, "rb"))){	
		printf( "ERROR - No se pudo cargar el archivo [%s]\n", szFilename);
		return false;
	}

	//Establecemos el tamaño del archivo
	int iStart = ftell(f);
	fseek(f, 0, SEEK_END);
	
	int iEnd = ftell(f);
	fseek(f, 0, SEEK_SET);
	iFileSize = iEnd - iStart;

	//Reservamos memoria
	ucpBuffer = new unsigned char[iFileSize];
	ucpPtr = ucpBuffer;

	if(!ucpBuffer)
	{	
		printf( "ERROR - No se pudo reservar memoria para [%s]\n", szFilename);
		return false;
	}

	//Leemos el archivo
	if(fread(ucpBuffer, 1, iFileSize, f) != (unsigned)iFileSize)
	{
		printf( "ERROR - No se pudo leer [%s]\n", szFilename);
		delete [] ucpBuffer;
		return false;
	}

	//Cerramos el archivo
	fclose(f);

	//Leemos la cabecera
	memcpy(&m_Cabecera, ucpPtr, sizeof(SCabeceraMD2));

	//Es un md2 valido?
	if(m_Cabecera.m_iNumMagico != 844121161 || m_Cabecera.m_iVersion != 8)
	{	
		printf( "ERROR - [%s] no es un md2 válido\n", szFilename);
		delete [] ucpBuffer;
		return false;
	}
	
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Cabecera.m_iOffsetFrames;
	
	m_Cabecera.m_iNumFrames=NUM_FRAMES;

	//Leemos los frames de animación
	m_pFrames = new SMD2Frame[m_Cabecera.m_iNumFrames];	
		
	for( i = 0; i < m_Cabecera.m_iNumFrames; i++)
	{	
		float fScale[3];
		float fTrans[3];
		m_pFrames[i].m_pVerts = new SMD2Vert[m_Cabecera.m_iNumVertices];
		
		//Expandimos los vertices
		memcpy(fScale, ucpTmpPtr, 12);
		memcpy(fTrans, ucpTmpPtr + 12, 12);
		memcpy(m_pFrames[i].m_caName, ucpTmpPtr + 24, 16);
		ucpTmpPtr += 40;
		for( j = 0; j < m_Cabecera.m_iNumVertices; j++){
		
			//Cambiamos la orientación
			//m_pFrames[i].m_pVerts[j].m_fVert[0] = (ucpTmpPtr[0] * fScale[0] + fTrans[0]);
			//m_pFrames[i].m_pVerts[j].m_fVert[1] = (ucpTmpPtr[2] * fScale[2] + fTrans[2]);
			//m_pFrames[i].m_pVerts[j].m_fVert[2] = (ucpTmpPtr[1] * fScale[1] + fTrans[1]);
			
			m_pFrames[i].m_pVerts[j].m_fVert[0] = (ucpTmpPtr[0] * fScale[0] + fTrans[0]);
			m_pFrames[i].m_pVerts[j].m_fVert[1] = (ucpTmpPtr[1] * fScale[1] + fTrans[1]);
			m_pFrames[i].m_pVerts[j].m_fVert[2] = (ucpTmpPtr[2] * fScale[2] + fTrans[2]);
		
			
			//Vamos calculando el max y min de X e Y
			if(m_pFrames[i].m_pVerts[j].m_fVert[0]>m_fmax_X)m_fmax_X=m_pFrames[i].m_pVerts[j].m_fVert[0];
			if(m_pFrames[i].m_pVerts[j].m_fVert[0]<m_fmin_X)m_fmin_X=m_pFrames[i].m_pVerts[j].m_fVert[0];
			if(m_pFrames[i].m_pVerts[j].m_fVert[2]>m_fmax_Y)m_fmax_Y=m_pFrames[i].m_pVerts[j].m_fVert[2];
			if(m_pFrames[i].m_pVerts[j].m_fVert[2]<m_fmin_Y)m_fmin_Y=m_pFrames[i].m_pVerts[j].m_fVert[2];
					
			m_pFrames[i].m_pVerts[j].m_ucReserved = ucpTmpPtr[3];
			ucpTmpPtr += 4;
		}
		
	}

	//Leemos los triángulos
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Cabecera.m_iOffsetTriangles;
	m_pTriangles = new SMD2Tri[m_Cabecera.m_iNumTriangulos];
	memcpy(m_pTriangles, ucpTmpPtr, 12 * m_Cabecera.m_iNumTriangulos);

	//Leemos las coordenadas de la textura
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Cabecera.m_iOffsetTexCoords;
	m_pTexCoords = new SMD2TexCoord[m_Cabecera.m_iNumTexCoords];
	
	short * sTexCoords = new short[m_Cabecera.m_iNumTexCoords * 2];
	memcpy(sTexCoords, ucpTmpPtr, 4 * m_Cabecera.m_iNumTexCoords);

	for(i = 0; i < m_Cabecera.m_iNumTexCoords; i++)
	{	
		m_pTexCoords[i].m_fTex[0] = (float)sTexCoords[2*i] / m_Cabecera.m_iAnchoSkin;
		m_pTexCoords[i].m_fTex[1] = (float)sTexCoords[2*i+1] / m_Cabecera.m_iAltoSkin;
	}
	
	//Calculamos las normales de los Vértices
	float NormalesCaras[4096][3];
	for(j = 0; j < m_Cabecera.m_iNumTriangulos; j++)
	{	
		NormalesCaras[j][0]=0;
		NormalesCaras[j][1]=0;
		NormalesCaras[j][2]=0;
			
		CalcularVectorNormal(m_pFrames[1].m_pVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert,
				 		 	 m_pFrames[1].m_pVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert,
						 	 m_pFrames[1].m_pVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert,
						 	 &NormalesCaras[j][0],&NormalesCaras[j][1],&NormalesCaras[j][2]);
		Normalizar(NormalesCaras[i]);
	}
		
	float SumX,SumY,SumZ;
	int NumCarasCompartidas,k;
		
	for(j=0;j< m_Cabecera.m_iNumVertices;j++)
	{			
		SumX=0;
		SumY=0;
		SumZ=0;
		NumCarasCompartidas=0;
			
		m_Normales[j][0]=0;
		m_Normales[j][1]=0;
		m_Normales[j][2]=0;
		
		for(k = 0; k < m_Cabecera.m_iNumTriangulos; k++)
		{
			if(	m_pTriangles[k].m_sVertIndices[0]==j ||
				m_pTriangles[k].m_sVertIndices[1]==j ||
				m_pTriangles[k].m_sVertIndices[2]==j)
			{
				SumX+=NormalesCaras[k][0];
				SumY+=NormalesCaras[k][1];
				SumZ+=NormalesCaras[k][2];					
				NumCarasCompartidas++;
			}			
		}
			
		m_Normales[j][0]=SumX/(float)NumCarasCompartidas;
		m_Normales[j][1]=SumY/(float)NumCarasCompartidas;
		m_Normales[j][2]=SumZ/(float)NumCarasCompartidas;
		Normalizar(m_Normales[j]);
	}


	// Creamos la Textura 1D	
	float shader[32][3]={{0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {0.3,0.3,0.3},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0}};
	/*
	float shader[32][3]={{0.2,0.2,0.2},
						 {0.2,0.2,0.2},
						 {0.2,0.2,0.2},
						 {0.2,0.2,0.2},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {0.5,0.5,0.5},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0},
						 {1.0,1.0,1.0}};
	*/
	glEnable (GL_TEXTURE_1D);				
	glGenTextures (1, &m_Textura1D);				
    glBindTexture (GL_TEXTURE_1D, m_Textura1D);
    glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);	
    glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shader);
	glDisable (GL_TEXTURE_1D);	
	
	
	delete [] sTexCoords;	
	
	printf( "+ [%s] cargado\n", szFilename);	
	delete [] ucpBuffer;
	return true;
}

/*------------------------------------------------------------------------------
	Free memory function
------------------------------------------------------------------------------*/
void CMd2::Liberar()
{
	if(m_pFrames)
	{
		delete [] m_pFrames;
		m_pFrames = 0;
	}
	if(m_pTexCoords)
	{
		delete [] m_pTexCoords;
		m_pTexCoords = 0;
	}
	if(m_pTriangles)
	{
		delete [] m_pTriangles;
		m_pTriangles = 0;
	}
	if(m_pVerts)
	{
		delete[] m_pVerts;
		m_pVerts = 0;
	}
	
	m_Skin.Liberar();
			
	printf("- MD2 Liberado \n");
}

/*------------------------------------------------------------------------------
	Skin model loader
------------------------------------------------------------------------------*/
bool CMd2::CargarSkin(const char * szFilename)
{
    if (!m_Skin.Cargar(szFilename))
    	return FALSE;

    return TRUE;
}

/*------------------------------------------------------------------------------ 
	Draws MD2 with cell shading effect
------------------------------------------------------------------------------*/
void CMd2::DibujarCelShading(unsigned int uiFrame)
{		
	float vNormal[3];
	float vTmp[3];
	float M[16];
	float fShade;	
	
	glDisable (GL_TEXTURE_2D);
	glEnable (GL_TEXTURE_1D);				
	glBindTexture (GL_TEXTURE_1D, m_Textura1D);	
			
	glGetFloatv (GL_MODELVIEW_MATRIX, M);	// Obtiene la matriz actual
	
	glBegin(GL_TRIANGLES);
	
	for(int x = 0; x < m_Cabecera.m_iNumTriangulos; x++)
	{		
		for(int y=0;y<3;y++)
		{	
			vNormal[0]=m_Normales[m_pTriangles[x].m_sVertIndices[y]][0];
			vNormal[1]=m_Normales[m_pTriangles[x].m_sVertIndices[y]][1];
			vNormal[2]=m_Normales[m_pTriangles[x].m_sVertIndices[y]][2];
		
			
			Rotar(M,vNormal,vTmp);
			Normalizar(vTmp);
			
			fShade = Angulo (vTmp, m_fAnguloLuz);
	
			if (fShade < 0.0f)
				fShade = 0.0f;
				
			glTexCoord1f (fShade);
			glVertex3fv(m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[y]].m_fVert);
			
		}
	}
	glEnd();		
	glDisable(GL_TEXTURE_1D);
	glEnable(GL_TEXTURE_2D);	
	glBindTexture (GL_TEXTURE_2D, 0);	
		
	if(!m_bDelineado)return;

	//glDepthFunc (GL_LEQUAL);
	glPolygonMode (GL_BACK, GL_LINE);	// Draw Backfacing Polygons As Wireframes ( NEW )
	glLineWidth (m_AnchoLinea);			// Set The Line Width ( NEW )
	glCullFace (GL_FRONT);				// Don't Draw Any Front-Facing Polygons ( NEW )
						// Set The Outline Color ( NEW )

	glColor3fv(m_ColorLinea);
	glBegin (GL_TRIANGLES);				// Tell OpenGL What We Want To Draw


	for(int x = 0; x < m_Cabecera.m_iNumTriangulos; x++)
	{		
		for(int y=0;y<3;y++)
		{	
		
			glVertex3fv(m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[y]].m_fVert);

/*			glVertex3f(m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[y]].m_fVert[0],
						m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[y]].m_fVert[1],
						m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[y]].m_fVert[2]);*/
		}
	}
	
	glEnd ();							// Tell OpenGL We've Finished
	glDepthFunc (GL_LESS);				// Reset The Depth-Testing Mode ( NEW )	
	glCullFace (GL_BACK);				// Reset The Face To Be Culled ( NEW )
	glPolygonMode (GL_BACK, GL_FILL);	// Reset Back-Facing Polygon Drawing Mode ( NEW )
}

/*------------------------------------------------------------------------------
	Draws MD2. First frame
------------------------------------------------------------------------------*/

void CMd2::Dibujar()
{	
	if(m_Skin.getCargada())
		m_Skin.Bind();				


	glBegin(GL_TRIANGLES);		
	for(int x = 0; x < m_Cabecera.m_iNumTriangulos; x++){
	
			glTexCoord2fv(m_pTexCoords[m_pTriangles[x].m_sTexIndices[0]].m_fTex);
			glVertex3fv(m_pFrames[0].m_pVerts[m_pTriangles[x].m_sVertIndices[0]].m_fVert);
			
			glTexCoord2fv(m_pTexCoords[m_pTriangles[x].m_sTexIndices[1]].m_fTex);
			glVertex3fv(m_pFrames[0].m_pVerts[m_pTriangles[x].m_sVertIndices[1]].m_fVert);
			
			glTexCoord2fv(m_pTexCoords[m_pTriangles[x].m_sTexIndices[2]].m_fTex);
			glVertex3fv(m_pFrames[0].m_pVerts[m_pTriangles[x].m_sVertIndices[2]].m_fVert);

			
		}
	glEnd();	

}

/*------------------------------------------------------------------------------
	Draws MD2.
------------------------------------------------------------------------------*/
void CMd2::Dibujar(unsigned int uiFrame)
{	
	if (m_Skin.getCargada())
		m_Skin.Bind();
		
	glBegin(GL_TRIANGLES);
	for(int x = 0; x < m_Cabecera.m_iNumTriangulos; x++)
	{
		glTexCoord2fv(m_pTexCoords[m_pTriangles[x].m_sTexIndices[0]].m_fTex);
		glVertex3fv(m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[0]].m_fVert);
		
		glTexCoord2fv(m_pTexCoords[m_pTriangles[x].m_sTexIndices[1]].m_fTex);
		glVertex3fv(m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[1]].m_fVert);
		
		glTexCoord2fv(m_pTexCoords[m_pTriangles[x].m_sTexIndices[2]].m_fTex);
		glVertex3fv(m_pFrames[uiFrame].m_pVerts[m_pTriangles[x].m_sVertIndices[2]].m_fVert);
	}
	glEnd();
}



